/*************************************************************************
 * Filename:    ProcessManager.c
 * Description: A process manager. Controls the transitions between 
 *              different states.
 * Group 2:     nachOS
 * Authors:     Ying Wang
 *              Daniel Fairweather
 *              Jan Durakiewicz
 * Class:       CSE 325
 * Instructor:  Mr. Duggan
 * Assignment:  Lab #3 Scheduler
 * Assigned:    Feburary 12, 2013
 * Due:         Feburary 26, 2013
 *************************************************************************/

#include "ProcessManager.h"
#include "QueueManager.h"

freelist fl_PM;  
int PID_current;

//The group id of the process that is currently running (in group fair share)
//- i.e. calling schedule_fs will get a process from the group after this
int CURRENT_GROUP = 0;

void initializeProcessManager(int scheduler_number){
    int i = 0;                           /* Incrementor for looping. */
    initalizeQueueManager();
    next_process = NULL;
    PID_current = -1;
    scheduler_algorithm = scheduler_number;    /* Set the scheduler. */
    fl_PM.length = FREE_SPACE;
    
    
    /* Link the reserved process_control_blocks together, forming the freelist linked list. */
    for(i = 0; i < FREE_SPACE - 1; i++){
        fl_PM.list[i].next = &fl_PM.list[i+1];
    }
    fl_PM.list[i].next = NULL;
    
    fl_PM.head = &fl_PM.list[0];               /* Assign the head pointer of the freelist. */
    fl_PM.tail = &fl_PM.list[FREE_SPACE-1];    /* Assign the tail pointer of the freelist. */
}



process_control_block* create(int pid, int gid, int psw, int page_table, int regs[NUM_REGS]){
    process_control_block* process; /* The process we will be creating. */
    int i = 0;
    /* /* If the pid is unique and positive, create a PCB and add it to the NEW queue. */
    if(pid >= 0 && isUniquePID(pid) == 1 && gid >= 1 && gid <= 3){
        process = getPCB();
        if(process != NULL){
            process->pid = pid;
            process->psw = psw;
            process->page_table = page_table;
            process->gid = gid;
            process->age = 0;
			process->mailbox = -1;
            for(i = 0; i < NUM_REGS; i++){
                process->regs[i] = regs[i];
            }
            
            enqueue(0, process);
            pReady(process->pid);
        }
        return(process);
    }
    
    /* If the pid was not unique or positive, return NULL. */
    return(NULL);
}



process_control_block* getPCB(){
    process_control_block* temp;
    
    /* Returns a NULL pointer if there's no available process_control_block in the freelist. */ 
    if(fl_PM.length == 0){
        return(NULL);
    }
    
    temp = fl_PM.head;                      /* Get the oldest element in the freelist. */
    fl_PM.head = fl_PM.head->next;             /* Adjust the head pointer of the freelist. */ 
    temp->next = NULL;                   /* Break the connection between the previous head element and the current one. */
    
    /* Adjust the tail pointer of the freelist if there's only one process_control_block in the freelist. */
    if(fl_PM.length == 1){
        fl_PM.head = NULL;
        fl_PM.tail = NULL;
    }
    
    fl_PM.length--;                         /* Decrement the length of the freelist. */
    return(temp);
}



void freePCB(process_control_block* target){
    /* Only free it if the process_control_block is not NULL. */
    if(target != NULL){
        if(fl_PM.length == 0){
            fl_PM.head = target;
        }
        else{
            fl_PM.tail->next = target;              /* Append the process_control_block to the end of the freelist. */
        }
        fl_PM.tail = target;                    
        fl_PM.tail->next = NULL;
        fl_PM.length++;                         /* Increment the length of the freelist. */ 
    }
}



process_control_block* pReady(int pid){
    process_control_block* temp = delqueue(0, pid);  /* Take the process_control_block of the given pid from the New queue. */
    enqueue(1,temp);                           /* Enqueue it to the Ready queue. */
    return(temp);
}



process_control_block* go(){
    process_control_block* temp = NULL;
    int incrementor = 0;
	
    /* Only put the process in the Running queue if the Running queue is currently empty. */
    if(isEmptyQueue(2) == 1){
        
        /* The first time go() is called, we need to schedule the next_process. */
        if(next_process==NULL){
            /* Call different schedulers based on the original user input, to schedule the next process. */
            if(scheduler_algorithm == 0){
                next_process = schedule_fs();
            }
            else if(scheduler_algorithm == 1){
                next_process = schedule_pr(0);
            }
        }
        if(next_process == NULL){
            return(NULL);
        }
        
        temp = next_process;
        delqueue(1,next_process->pid);        /* Delete the scheduled process from the Ready queue. */
        enqueue(2,next_process);               /* Enqueue the scheduled process to the Running queue. */
        
        if(scheduler_algorithm == 0){
            next_process = schedule_fs();
        }
        else if(scheduler_algorithm == 1){
            next_process = schedule_pr(1);
        }
        return(temp);
    }
    return(NULL);
}



process_control_block* schedule_pr(int flag){
    process_queue* ready = &queues[1];             /* Get the ready queue. */
    process_control_block* process = ready->head;  /* Start from the head of the Ready queue. */
    int highest_priority = 0;                      /* The highest total priority in the queue. */
    int current_priority = 0;                      /* The total priority of the current process. */
    process_control_block* highest = NULL;         /* The pointer to the process of the highest total priority. */
    
    while(process != NULL){
        current_priority = process->age + process->gid*10;  /* Calculate the total priority of the current process. */
        /* Update highest_priority and highest* if the current total priority is higher. */
        if(highest_priority < current_priority){
            highest_priority = current_priority;
            highest = process;
        }
        if(flag == 1){
            process->age++;                        /* Update the process's age. */
        }
        process = process->next;                   /* Go to the next process in the queue. */
    }    
    return(highest);
}



process_control_block* eoQuantum(){
    process_control_block* temp = dequeue(2);  /* Take the process_control_block from the Running queue. */
    if(temp != NULL){
        temp->age = 0;
        enqueue(1,temp);                       /* Enqueue it to the Ready queue. */
        if(scheduler_algorithm == 1){
            next_process = schedule_pr(0);
        }
    }
    return(temp);
}



process_control_block* wait(){
    process_control_block* temp = dequeue(2);  /* Take the process_control_block from the Running queue. */
    enqueue(3,temp);                           /* Enqueue it to the Waiting queue. */
    return(temp);
}



process_control_block* unwait(int pid){
    process_control_block* temp = delqueue(3, pid);  /* Take out the process_control_block of the given pid from the Waiting queue, if any. */
    if(temp != NULL){
        temp->age = 0;
        enqueue(1,temp);                       /* Enqueue it to the Ready queue. */
        if(scheduler_algorithm == 1){
            next_process = schedule_pr(0);
        }
    }
    return(temp);
}



process_control_block* eoLife(){
    process_control_block* temp = dequeue(2);  /* Take the process_control_block from the Running queue. */
    enqueue(4,temp);                           /* Enqueue it to the Terminated queue. */
    dequeue(4);                                /* Dequeue it from the Terminated queue. */
    freePCB(temp);                             /* Free the process_control_block. */
    return(temp);
}



process_control_block* terminate(int pid){
    process_control_block* temp;
    temp = search(2,pid);
    if (temp == NULL)
    {
        temp = delqueue(1, pid);  /* Take out the process_control_block of the given pid from the Ready queue, if any. */
        if(temp == NULL){
            temp = delqueue(3, pid);                     /* Take out the process_control_block of the given pid from the Waiting queue, if any. */
        }
        enqueue(4,temp);                                 /* Enqueue it to the Terminated queue. */
        dequeue(4);                                      /* Dequeue it from the Terminated queue. */
        freePCB(temp);                                   /* Free the process_control_block. */
        return(temp);
    }
    else{
        return(eoLife());
    }
}



process_control_block* schedule_fs(){
    //get the first element in the ready queue
    process_control_block* temp = (&queues[1])->head;

    //increment the group counter, looping around at 3 (Gets groups 1, 2, 3)
    CURRENT_GROUP = (CURRENT_GROUP%3)+1;
    
    //check if there's anything there
    if(temp == NULL)
        return NULL;

    //look for the first instance of a pcb with the currently scheduled GID
    while(temp != NULL){
        //return if it's the right one
        if(temp->gid == CURRENT_GROUP){
            return temp;
        }
        temp = temp->next;
    }
    
    //if none have been found for that group, get the next group
    return schedule_fs();
}



int getUniquePID(){
    if(PID_current < MAX_PID){
        PID_current++;
        return(PID_current);
    }
    else{
        PID_current = 0;
        while(isUniquePID(PID_current)==0)
        {
            PID_current++;
        }
    }
    return(PID_current);
}

int setPriority(process_control_block* pcb, int priority){
	if(priority >= 1 && priority <= 3 && pcb != NULL){
		pcb->gid = priority;
		return priority;
	}
	else if (pcb == NULL){
		return(-1);
	}
	else{
		return(-2);
	}
}